//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Copyright 2012 Microsoft Corporation. All Rights Reserved
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.   
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

namespace Preflight.Security
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Security.Cryptography;
    using System.Text;
    using System.Web;

    public class SwtTokenValidator : ISwtTokenValidator
    {
        private const string IssuerKey = "Issuer";
        private const string AudienceKey = "Audience";
        private const string ExpirationKey = "ExpiresOn";
        private const string HmacSignatureKey = "HMACSHA256";

        private readonly string issuer;
        private readonly string audience;
        private readonly HMACSHA256 hashCalculator;

        public SwtTokenValidator(string issuer, string audience, string signingKey)
        {
            this.issuer = issuer;
            this.audience = audience;
            this.hashCalculator = new HMACSHA256(Convert.FromBase64String(signingKey));
        }

        private ulong EpochTime
        {
            get
            {
                var difference = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
                return Convert.ToUInt64(difference.TotalSeconds);
            }
        }

        public bool IsTokenValid(string token)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(token))
                {
                    Log.LogError("Token is null cannot validate");
                    return false;
                }

                var values = this.ParseToken(token);
                return 
                    this.HasAllRequiredKeys(values) &&
                    this.IsSignatureValid(token) &&
                    !this.IsExpired(values) && 
                    this.HasValidIssuer(values) && 
                    this.HasValidAudience(values);
            }
            catch (TokenParsingException exception)
            {
                Log.LogError("Could not parse token because: {0}. Token Value: {1}", exception, token);
                return false;
            }
        }

        private bool IsSignatureValid(string token)
        {
            var seperator = string.Format(CultureInfo.InvariantCulture, "&{0}=", HmacSignatureKey);
            var parts = token.Split(new[] { seperator }, StringSplitOptions.None);
            var tokenWithoutSignature = Encoding.ASCII.GetBytes(parts[0]);
            var signature = parts[1];
            var actualHash = this.hashCalculator.ComputeHash(tokenWithoutSignature);

            var actualSignature = HttpUtility.UrlEncode(Convert.ToBase64String(actualHash));
            if (signature != actualSignature)
            {
                Log.LogVerbose("Token signature did not match");
                return false;
            }

            return true;
        }

        private bool IsExpired(Dictionary<string, string> values)
        {
            var value = Convert.ToUInt64(values[ExpirationKey]);
            if (this.EpochTime > value)
            {
                Log.LogVerbose("Token has expired");
                return true;
            }

            return false;
        }

        private bool HasValidIssuer(Dictionary<string, string> values)
        {
            var value = values[IssuerKey];
            return string.Compare(value, this.issuer, StringComparison.InvariantCultureIgnoreCase) == 0;
        }

        private bool HasValidAudience(Dictionary<string, string> values)
        {
            var value = values[AudienceKey];
            return string.Compare(value, this.audience, StringComparison.InvariantCultureIgnoreCase) == 0;
        }

        private bool HasAllRequiredKeys(Dictionary<string, string> values)
        {
            var keys = new[] { AudienceKey, IssuerKey, ExpirationKey, HmacSignatureKey };
            foreach (var key in keys)
            {
                if (!values.ContainsKey(key))
                {
                    Log.LogVerbose("Token is missing key: {0}", key);
                    return false;
                }
            }

            return true;
        }

        private Dictionary<string, string> ParseToken(string token)
        {
            var parser = new TokenFormatter();
            return parser.Parse(token);
        }
    }
}
